package com.allen.app.common.utils;

import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.collection.CollUtil;
import java.beans.BeanInfo;
import java.beans.IntrospectionException;
import java.beans.Introspector;
import java.beans.PropertyDescriptor;
import java.io.StringReader;
import java.io.StringWriter;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.lang.reflect.Proxy;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.function.BiConsumer;
import java.util.function.BinaryOperator;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Supplier;
import java.util.stream.Collector;
import java.util.stream.Stream;
import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Marshaller;
import lombok.Data;
import org.springframework.util.ClassUtils;
import org.springframework.util.ObjectUtils;
import org.springframework.util.ReflectionUtils;

/**
 * java bean对象操作工具<br/> <ul> 规则： <li>将目标对象非空属性复制到源对象对应的属性</li> <li>支持填充和覆盖。填充(cover =
 * false)：只填充源对象中<strong>值为空的属性</strong>；覆盖(cover = true)：<strong>无论源对象属性是否有值</strong>都将覆盖</li> <li>支持自定义异常处理。</li>
 * <li>支持javaBean、Map。Map也可以使用{@link Maps}类</li> </ul> <code>Beans.from(目标对象...).[and(目标对象2)].[ifError(log::error)].[excludes(排除属性...)].to(copy值到源对象,
 * 是否强制覆盖)</code>
 */
public class Beans {

    private Set<String> excludes = new HashSet<>();
    private Set<Property> propertySet = new HashSet<>();
    private Consumer<Throwable> errorHandler = throwable -> {
    };

    private Beans() {
        this.excludes.add("class");
    }

    @SuppressWarnings("unchecked")
    public static <T> T newInstanse(Class clazz, String fieldArrStr, Object... valArr) {
        if (Objects.isNull(clazz)) {
            return null;
        }
        if (CheckObjectUtils.isNull(fieldArrStr)) {
            //noinspection Convert2MethodRef
            return (T) Try.unchecked(() -> clazz.newInstance());
        }
        String[] fieldArr = fieldArrStr.split(",");
        Map<String, Object> objMap = new HashMap<>();
        for (int i = 0; i < fieldArr.length; i++) {
            if (CheckObjectUtils.isNull(fieldArr[i])) {
                continue;
            }
            if (i < valArr.length) {
                objMap.put(fieldArr[i], valArr[i]);
            }
        }
        return (T) Jackson.object(objMap, clazz);
    }

    public static Beans from(Object... target) {
        Beans instance = new Beans();
        Arrays.stream(target).forEach(instance::and);
        return instance;
    }

    public Beans and(Object target) {
        if (target instanceof Empty) {
            return this;
        }
        if (target instanceof Map) {
            Maps.transform((Map<?, ?>) target).forEach((key, value) -> {
                if (Objects.nonNull(value)) {
                    this.propertySet.add(new Property((String) key, value));
                }
            });
            return this;
        }
        BeanInfo beanInfo = null;
        try {
            beanInfo = Introspector.getBeanInfo(target.getClass());
        } catch (IntrospectionException e) {
            this.errorHandler.accept(e);
        }
        if (Objects.nonNull(beanInfo)) {
            Stream.of(beanInfo.getPropertyDescriptors())
                .filter(descriptor -> !excludes.contains(descriptor.getName()))
                .forEach(descriptor -> {
                    Object value = ReflectionUtils.invokeMethod(descriptor.getReadMethod(), target);
                    if (Objects.nonNull(value)) {
                        this.propertySet.add(new Property(descriptor.getName(), value));
                    }
                });
        }
        return this;
    }

    /**
     * 转map 会忽略空字段
     * @param source bean对象
     * @return Map
     */
    public static Map<String, Object> toMap(Object source) {
        return from(source).propertySet.stream().collect(ForceToMapCollector.of(Property::getName, Property::getValue));
    }

    /**
     * 对象转Map(含继承) 可指定驼峰或是否忽略null
     *
     * @param source bean对象
     * @param isToUnderlineCase 是否转换为下划线模式
     * @param ignoreNullValue 是否忽略值为空的字段
     * @return Map
     */
    public static Map<String, Object> toMap(Object source, boolean isToUnderlineCase, boolean ignoreNullValue) {
        return BeanUtil.beanToMap(source, isToUnderlineCase, ignoreNullValue);
    }

    public static String toXml(Object source) {
        StringWriter writer = new StringWriter();
        try {
            Marshaller marshaller = JAXBContext.newInstance(source.getClass()).createMarshaller();
            marshaller.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, false);
            marshaller.setProperty(Marshaller.JAXB_ENCODING, "UTF-8");
            marshaller.marshal(source, writer);
        } catch (Exception e) {
            Exceptions.report(e.getMessage(), e);
        }
        return writer.toString().replace("standalone=\"yes\"", "");
    }

    public static Beans fromXml(String xml, Class<?> type) {
        try {
            return from(
                JAXBContext.newInstance(type).createUnmarshaller().unmarshal(new StringReader(xml))
            );
        } catch (JAXBException e) {
            Exceptions.report(e.getMessage(), e);
        }
        return empty();
    }

    public static Beans empty() {
        return from(new Empty());
    }

    public boolean isEmpty() {
        return this.propertySet.isEmpty();
    }

    public static String toJson(Object source) {
        return Jackson.json(source);
    }

    public Beans excludes(String... fieldNames) {
        Arrays.stream(fieldNames)
            .forEach(name -> this.propertySet.removeIf(property -> Objects.equals(property.name, name)));
        return this;
    }

    public Beans assign(String... fieldNames) {
        ArrayList<String> fildNameList = CollUtil.toList(fieldNames);
        this.propertySet.removeIf(property -> !fildNameList.contains(property.name));
        return this;
    }

    public Map<String, Object> toMap() {
        return this.propertySet.stream().collect(ForceToMapCollector.of(Property::getName, Property::getValue));
    }

    /**
     * 转Map(含继承) 可指定驼峰或是否忽略null
     *
     * @param isToUnderlineCase 是否转换为下划线模式
     * @param ignoreNullValue 是否忽略值为空的字段
     * @return Map
     */
    public Map<String, Object> toMap(boolean isToUnderlineCase, boolean ignoreNullValue) {
        return  BeanUtil.beanToMap(this.propertySet,isToUnderlineCase,ignoreNullValue);
    }

    public Beans ifError(Consumer<Throwable> consumer) {
        this.errorHandler = consumer;
        return this;
    }

    public <T> T to(Supplier<T> supplier) {
        return to(supplier.get());
    }

    public <T> T to(Supplier<T> supplier, boolean cover) {
        return to(supplier.get(), cover);
    }

    public <T> T to(T source) {
        return to(source, true);
    }

    public <T> T to(Class clazz) {
        if (Objects.isNull(clazz)) {
            return null;
        }
        //noinspection unchecked
        return (T) Try.unchecked(() -> to(clazz.newInstance(), true));
    }

    public <T> T to(T source, boolean cover) {
        this.propertySet.forEach(property -> property.copyTo(source, cover, this.errorHandler));
        return source;
    }

    public static Object invokeMethod(Class<?> interfaceClass, String defaultMethodName) {
        return invokeMethod(interfaceClass, defaultMethodName, null);
    }

    public static Object invokeMethod(Class<?> cls, String methodName, Object instance) {
        Method method = ReflectionUtils.findMethod(cls, methodName);
        Objects.requireNonNull(method);

        Object target = instance;
        if (cls.isInterface()) {
            InterfaceDefaultMethodProxy invocationHandler = new InterfaceDefaultMethodProxy();
            target = Proxy.newProxyInstance(cls.getClassLoader(), new Class[]{cls}, invocationHandler);
        }
        return ReflectionUtils.invokeMethod(method, target);
    }


    @Data
    public static class Property {

        private String name;
        private Object value;

        private Property(String name, Object value) {
            Args.notNull(name, "name");
            this.name = name;
            this.value = value;
        }

        @SuppressWarnings("unchecked")
        void copyTo(Object object, boolean force, Consumer<Throwable> errorHandler) {
            if (object instanceof Map) {
                ((Map) object).put(this.name, this.value);
                return;
            }

            PropertyDescriptor descriptor = null;
            try {
                descriptor = new PropertyDescriptor(this.name, object.getClass());
            } catch (IntrospectionException e) {
                errorHandler.accept(e);
            }
            if (Objects.nonNull(descriptor)) {
                Object value = ReflectionUtils.invokeMethod(descriptor.getReadMethod(), object);
                if (!force && Objects.nonNull(value)) {
                    return; // 非强制copy时，目标属性有值则不处理
                }
                this.invokeSetter(object, descriptor.getWriteMethod(), this.value, errorHandler);
            }
        }

        private void invokeSetter(Object instance, Method setMethod, Object value, Consumer<Throwable> errorHandler) {
            Class<?> paramType = setMethod.getParameterTypes()[0];
            Class<?> valueType = value.getClass();
            try {
                if (Objects.equals(paramType, valueType)) {  // 参数类型与值类型相等
                    ReflectionUtils.invokeMethod(setMethod, instance, value);
                } else if (String.class.isAssignableFrom(paramType)) { // 参数为String，值或为枚举
                    ReflectionUtils.invokeMethod(setMethod, instance,
                        value instanceof Enum ? ((Enum) value).name() : value.toString());
                } else if (paramType.isPrimitive() || Number.class.isAssignableFrom(paramType)) { // 参数为基本数据类型或数字类型
                    Constructor<?> constructor = ClassUtils.getConstructorIfAvailable(paramType, String.class);
                    if (Objects.nonNull(constructor)) {
                        ReflectionUtils.invokeMethod(setMethod, instance, constructor.newInstance(value.toString()));
                    }
                } else if (paramType.isEnum()) {  // 参数类型为枚举，值类型或为字符串、下标
                    if (String.class.isAssignableFrom(valueType)) {  // 值类型为字符串
                        @SuppressWarnings("unchecked")
                        Enum<?> enumValue = Enum.valueOf((Class) paramType, (String) value);
                        ReflectionUtils.invokeMethod(setMethod, instance, enumValue);
                    } else if (Integer.class.isAssignableFrom(valueType)) { // 值类型为下标
                        Object[] enumConstants = paramType.getEnumConstants();
                        if (!ObjectUtils.isEmpty(enumConstants)) {
                            ReflectionUtils.invokeMethod(setMethod, instance, enumConstants[(Integer) value]);
                        }
                    }
                } else {
                    ReflectionUtils.invokeMethod(setMethod, instance, value);
                }
            } catch (Throwable throwable) {
                errorHandler.accept(throwable);
            }
        }
    }

    public static class InterfaceDefaultMethodProxy implements InvocationHandler {

        @Override
        public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
            if (!method.isDefault()) {
                throw new UnsupportedOperationException("Only supported interface default method .");
            }
            Constructor<MethodHandles.Lookup> constructor = MethodHandles.Lookup.class
                .getDeclaredConstructor(Class.class, int.class);
            constructor.setAccessible(true);
            Class<?> declaringClass = method.getDeclaringClass();
            int allModes = MethodHandles.Lookup.PUBLIC | MethodHandles.Lookup.PRIVATE | MethodHandles.Lookup.PROTECTED
                | MethodHandles.Lookup.PACKAGE;
            return constructor.newInstance(declaringClass, allModes)
                .unreflectSpecial(method, declaringClass)
                .bindTo(proxy)
                .invokeWithArguments(args);
        }
    }

    static final class ForceToMapCollector<T, K, V> implements Collector<T, Map<K, V>, Map<K, V>> {

        private Function<? super T, ? extends K> keyMapper;
        private Function<? super T, ? extends V> valueMapper;

        ForceToMapCollector(Function<? super T, ? extends K> keyMapper, Function<? super T, ? extends V> valueMapper) {
            super();
            this.keyMapper = keyMapper;
            this.valueMapper = valueMapper;
        }

        @Override
        public BiConsumer<Map<K, V>, T> accumulator() {
            return (map, element) -> map.put(keyMapper.apply(element), valueMapper.apply(element));
        }

        @Override
        public Supplier<Map<K, V>> supplier() {
            return HashMap::new;
        }

        @Override
        public BinaryOperator<Map<K, V>> combiner() {
            return null;
        }

        @Override
        public Function<Map<K, V>, Map<K, V>> finisher() {
            return null;
        }

        @Override
        public Set<Characteristics> characteristics() {
            return Collections.unmodifiableSet(EnumSet.of(Characteristics.IDENTITY_FINISH));
        }

        public static <T, K, V> ForceToMapCollector<T, K, V> of(Function<? super T, ? extends K> keyMapper,
            Function<? super T, ? extends V> valueMapper) {
            return new ForceToMapCollector<>(keyMapper, valueMapper);
        }
    }

    private static final class Empty {

    }
}
